﻿using System;
using System.Windows.Forms;

using OpenTK.Graphics.OpenGL;

namespace GLSL
{
    /// <summary>
    /// Creates and Manages Frame Buffer Objects
    /// </summary>
    public class FBO
    {   
        private int fboHandle;
        private int colorTexture;
        private int depthRenderbuffer;

        private int width;
        private int height;

        public int TextureID
        {
            get
            {
                return colorTexture;
            }
        }

        public FBO(int width, int height)
        {
            this.width = width;
            this.height = height;

            if (!GL.GetString(StringName.Extensions).Contains("EXT_framebuffer_object"))
            {
                MessageBox.Show(string.Format("Your video card does not support Framebuffer Objects."
                    + Environment.NewLine
                    + "Please update your drivers."),
                    "FBOs not supported",
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Error
                    ); 
                return;
            }

            // Gen a texture
            GL.GenTextures(1, out colorTexture);
            GL.BindTexture(TextureTarget.Texture2D, colorTexture);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Nearest);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Nearest);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Clamp);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Clamp);
            GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba8, width, height, 0, 
                PixelFormat.Rgba, PixelType.UnsignedByte, IntPtr.Zero);

            // Flush to prevent feedback
            GL.BindTexture(TextureTarget.Texture2D, 0);

            // create a depth buffer
            GL.Ext.GenRenderbuffers(1, out depthRenderbuffer);
            GL.Ext.BindRenderbuffer(RenderbufferTarget.RenderbufferExt, depthRenderbuffer);
            GL.Ext.RenderbufferStorage(RenderbufferTarget.RenderbufferExt, (RenderbufferStorage)All.DepthComponent32,
                width, height);


            GL.Ext.GenFramebuffers(1, out fboHandle);
            GL.Ext.BindFramebuffer(FramebufferTarget.FramebufferExt, fboHandle);

            GL.Ext.FramebufferTexture2D(FramebufferTarget.FramebufferExt, 
                FramebufferAttachment.ColorAttachment0Ext,
                TextureTarget.Texture2D, colorTexture, 0);

            GL.Ext.FramebufferRenderbuffer(FramebufferTarget.FramebufferExt, 
                FramebufferAttachment.DepthAttachmentExt,
                RenderbufferTarget.RenderbufferExt, depthRenderbuffer);

            //Reset the FBO
            GL.Ext.BindFramebuffer(FramebufferTarget.FramebufferExt, 0);
        }


        public void Bind()
        {  
            GL.Ext.BindFramebuffer(FramebufferTarget.FramebufferExt, fboHandle);
            GL.DrawBuffer((DrawBufferMode)FramebufferAttachment.ColorAttachment0Ext);
            GL.PushAttrib(AttribMask.ViewportBit); // stores GL.Viewport() parameters

            GL.Viewport(0, 0, width, height);
            GL.ClearColor(0f, 0f, 0f, 1f);           
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
        }

        public void UnBind()
        {
            GL.PopAttrib(); // restores GL.Viewport() parameters         
            GL.Ext.BindFramebuffer(FramebufferTarget.FramebufferExt, 0); // return to visible framebuffer
            GL.DrawBuffer(DrawBufferMode.Back);
        }

        private bool checkError()
        {
            string errorLine = string.Empty;
            bool isError = true;

            switch (GL.Ext.CheckFramebufferStatus(FramebufferTarget.FramebufferExt))
            {
                case FramebufferErrorCode.FramebufferCompleteExt:
                    {
                        errorLine = "FBO: The framebuffer is complete and valid for rendering.";
                        isError = false;
                        break;
                    }
                case FramebufferErrorCode.FramebufferIncompleteAttachmentExt:
                    {
                        errorLine = "FBO: One or more attachment points are not framebuffer attachment complete. This could mean there’s no texture attached or the format isn’t renderable. For color textures this means the base format must be RGB or RGBA and for depth textures it must be a DEPTH_COMPONENT format. Other causes of this error are that the width or height is zero or the z-offset is out of range in case of render to volume.";
                        break;
                    }
                case FramebufferErrorCode.FramebufferIncompleteMissingAttachmentExt:
                    {
                        errorLine = "FBO: There are no attachments.";
                        break;
                    }
                case FramebufferErrorCode.FramebufferIncompleteDimensionsExt:
                    {
                        errorLine = "FBO: Attachments are of different size. All attachments must have the same width and height.";
                        break;
                    }
                case FramebufferErrorCode.FramebufferIncompleteFormatsExt:
                    {
                        errorLine = "FBO: The color attachments have different format. All color attachments must have the same format.";
                        break;
                    }
                case FramebufferErrorCode.FramebufferIncompleteDrawBufferExt:
                    {
                        errorLine = "FBO: An attachment point referenced by GL.DrawBuffers() doesn’t have an attachment.";
                        break;
                    }
                case FramebufferErrorCode.FramebufferIncompleteReadBufferExt:
                    {
                        errorLine = "FBO: The attachment point referenced by GL.ReadBuffers() doesn’t have an attachment.";
                        break;
                    }
                case FramebufferErrorCode.FramebufferUnsupportedExt:
                    {
                        errorLine = "FBO: This particular FBO configuration is not supported by the implementation.";
                        break;
                    }
                default:
                    {
                        errorLine = "FBO: Status unknown (fbo disabled)";
                        break;
                    }
            }

            return isError;
        }
    }
}
